package cs235.boggle;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.StringWriter;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;

//import org.apache.log4j.Logger;

public class PlayBoggle implements BogglePlayer {

//    private static Logger logger = Logger.getLogger(PlayBoggle.class);

    private String[] dictionary;
    private SortedSet<String> wordsFound;
    private int minWordLength;
    private Stack<Integer> position;
    private int gameBoardSize;


    private String[][] gameBoard;

    private String searchingForWord;

    public PlayBoggle() {
        wordsFound = new TreeSet<String>();
    }

    private boolean addPosition(int row, int col) {
        int currentPosition = row * gameBoardSize + col;
        if (position.contains(currentPosition)) {
//            logger.debug(this.position + "\t" + currentPosition);
            return false;
        }
        position.push(currentPosition);
        return true;
    }


    private int changeCol(int column, MovePositions move) {
        int newCol = column + move.changeColumn();
        if (newCol >= gameBoardSize || newCol < 0) {
            throw new ArrayIndexOutOfBoundsException("Column is out of place: " + newCol);
        }
//        logger.debug("Current col: " + column + "\tNew col: " + newCol);
        return newCol;
    }

    private int changeRow(int row, MovePositions move) {
        int newRow = row + move.changeRow();
        if (newRow >= gameBoardSize || newRow < 0) {
            throw new ArrayIndexOutOfBoundsException("Row is out of place: " + newRow);
        }
//        logger.debug("Current row: " + row + "\tNew row: " + newRow);
        return newRow;
    }

    @Override
    public SortedSet<String> getAllValidWords(int minimumWordLength) {
        if (dictionary == null) {
            throw new IllegalStateException("The dictionary hasn't been loaded yet");
        } else if (gameBoard == null) {
            throw new IllegalStateException("The gameboard has not been set");
        }
        minWordLength = minimumWordLength;
        wordsFound = new TreeSet<String>();
        for (int row = 0; row < gameBoardSize; row++) {
            for (int column = 0; column < gameBoardSize; column++) {
                String first = gameBoard[row][column];
                position = new Stack<Integer>();
                position.add(row * gameBoardSize + column);
//                logger.debug("First = " + first + "\tAt: [" + row + ":" + column + "]");
                try {
                    searchBoard(row, column, first);
                } catch (FoundWord e){
//                    logger.info(e.getMessage());
                }
            }
        }
//        logger.debug("Found these words: " + wordsFound);
        return wordsFound;
    }

    @Override
    public String[] getCustomBoard() {
        String[][] custom = {
                {
                        "P", "R", "D", "E", "O", "T", "L", "J", "E", "I", "E", "A", "C", "O", "R",
                        "N"
                },
                {
                        "S", "T", "A", "R", "W", "A", "R", "S", "I", "S", "C", "O", "O", "L", "I",
                        "I"
                }
        };
        return custom[new Random().nextInt(custom.length)];
    }

    @Override
    public List<Integer> isOnBoard(String wordToCheck) {
        getAllValidWords(wordToCheck.length());
        searchingForWord = wordToCheck;
        for (int row = 0; row < gameBoardSize; row++) {
            for (int column = 0; column < gameBoardSize; column++) {
                String current = gameBoard[row][column];
                if (current.charAt(0) == searchingForWord.charAt(0)) {
                    position = new Stack<Integer>();
                    addPosition(row, column);
                    try{
                        searchBoard(row, column, current);
                    } catch (FoundWord e){
                        searchingForWord = null;
                        return Arrays.asList(position.toArray(new Integer[position.size()]));
                    }
                }
            }
        }
        searchingForWord = null;
        return null;
    }

    @Override
    public boolean isValidPrefix(String prefixToCheck) {
        if (dictionary == null) {
            throw new IllegalStateException("The dictionary hasn't been loaded yet");
        }

        int result = Math.abs(Arrays.binarySearch(dictionary, prefixToCheck));
        for (int i=-1;i<2;i++){
            String dictionaryWord = dictionary[result + i];
            if (dictionaryWord.startsWith(prefixToCheck)){
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isValidWord(String wordToCheck) {
        if (dictionary == null) {
            throw new IllegalStateException("The dictionary hasn't been loaded yet");
        }
        return Arrays.binarySearch(dictionary, wordToCheck) > 0;
    }

    @Override
    public void loadDictionary(String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("No Dictionary Provided");
        }
        try {
            Scanner scan = new Scanner(new File(fileName));
            SortedSet<String> hello = new TreeSet<String>();
            while (scan.hasNext()) {
                hello.add(scan.nextLine());
            }
            dictionary = hello.toArray(new String[hello.size()]);
        } catch (FileNotFoundException e1) {
            throw new IllegalArgumentException("File not found");
        }
    }

    private String printBoard() {
        StringWriter writer = new StringWriter();
        String newLine = System.getProperty("line.separator");
        writer.write(newLine);
        String border = "--" + repeat("--", gameBoardSize) + "-";
        writer.write(border);
        writer.write(newLine);
        for (int row = 0; row < gameBoardSize; row++) {
            writer.write("| ");
            for (int column = 0; column < gameBoardSize; column++) {
                writer.write(gameBoard[row][column] + " ");
            }
            writer.write("|" + newLine);
        }
        writer.write(border);
        writer.write(newLine);
        return writer.toString();
    }

    private void removePosition() {
//        logger.debug(position + "\t(" + currentPosition + ")");
        position.pop();
    }

    private String repeat(String toRepeat, int times) {
        String part = toRepeat;
        for (int i = 0; i < times - 1; i++) {
            toRepeat += part;
        }
        return toRepeat;
    }

    private void searchBoard(int row, int column, String current) throws FoundWord {
        for (MovePositions move : EnumSet.allOf(MovePositions.class)) {
            int tempRow = row;
            int tempCol = column;
            try {
                tempRow = changeRow(tempRow, move);
                tempCol = changeCol(tempCol, move);
            } catch (ArrayIndexOutOfBoundsException e) {
//                logger.debug("Invalid Move: " + move);
                continue;
            }

            String temp = current + gameBoard[tempRow][tempCol];
            if (!addPosition(tempRow, tempCol)) {
//                logger.debug("Repeated Move: " + move + "\tAt[" + row + ":" + column + "]("
//                        + current + ")");
                continue;
            }

//            logger.debug("Valid Move: " + move + "\tAt[" + row + ":" + column + "](" + current
//                    + ")");
            
            if (searchingForWord != null){
                if ( !searchingForWord.startsWith(temp)){
                    removePosition();
                    continue;
                }
                if (searchingForWord.equalsIgnoreCase(temp)) {
                    throw new FoundWord("Found word = " + searchingForWord);
                }
            } else {
    
                if (!isValidPrefix(temp)) {
//                    logger.debug("Invalid word = " + temp);
                    removePosition();
                    continue;
                } else 
                
                if (isValidWord(temp) && temp.length() >= minWordLength){
                    wordsFound.add(temp);
                }
                    
            }

            searchBoard(tempRow, tempCol, temp);
            removePosition();
        }
    }


    @Override
    public void setBoard(String[] letterArray) {
        if (letterArray == null) {
            throw new InvalidParameterException("LetterArray cannot be null");
        }
        Double length = new Double(letterArray.length);
        int size = (int) Math.sqrt(length);
        if ((length % size) != 0) {
            throw new InvalidParameterException("Board size is not square");
        }
        gameBoard = new String[size][size];
        gameBoardSize = size;
        int row = 0;
        int column = 0;
        for (int position = 0; position < length.intValue(); position++) {
            if (position % size == 0 && position != 0) {
                row++;
                column = 0;
            }
            gameBoard[row][column++] = letterArray[position].toLowerCase();
        }
//        logger.info(printBoard());
    }
}
